// ==UserScript==
// @name         YY-审核助手
// @namespace             https://gitee.com/monkeyshow/tm_scripts
// @supportURL            https://gitee.com/monkeyshow/tm_scripts
// @updateURL             https://gitee.com/monkeyshow/tm_scripts/raw/master/yy-label/yy-label-audit.js
// @downloadURL           https://gitee.com/monkeyshow/tm_scripts/raw/master/yy-label/yy-label-audit.js
// @version      0.9.0
// @description  YY-审核助手，辅助审核记录
// @author       jianjian.huang
// @match        *://label.yy.com/*.wav
// @grant        unsafeWindow
// @grant        GM_addStyle 
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_xmlhttpRequest
// @run-at       document-idle
// @require      https://cdn.staticfile.org/jquery/3.5.0/jquery.min.js
// @require      http://html2canvas.hertzen.com/dist/html2canvas.min.js
// ==/UserScript==
(() => {
  'use strict'
  const KEY_BOARD_CODE = { ENTER: 13, MINI_PLUS: 43, MINI_MINUS: 45, ZERO: 48, P: 80, R: 82, W: 87 };
  const WRONG_REASON = ['无', '切分错误', '文本错误', '浪费有效区间', '标点错误', '无效转有效'];
  const KEY_CODE_RIGHT = KEY_BOARD_CODE.R;// R
  const KEY_CODE_WRONG = KEY_BOARD_CODE.W;// W
  const RESULT_WAITING = 'WAITING';
  const RESULT_RIGHT = 'PASS';
  const RESULT_WRONG = 'ERROR';
  // /lifewood128/containers/SY-Batch225/sample/e851a685ce57f48573b1e9678bdb7708.wav
  const pathname = location.pathname; // 资源路径
  const pathArray = pathname.split('/');// 使用/分割成数组
  const uname = pathArray[1];
  const batchId = pathArray[3];// 获取批次id
  const role_type = pathArray[4]; // 获得url类型，目前已知label和simple
  const wavId = pathArray[5];// 获取音频id
  const label_page_url = `https://label.yy.com/api/containers/${batchId}/blobs?pageSize=1000`;
  const sample_page_url = `https://label.yy.com/api/containers/${batchId}/samples?pageSize=1000`;
  const MouseClickEvent = document.createEvent("MouseEvents");
  MouseClickEvent.initEvent("click", true, true);
  function getValue(name) {
    return GM_getValue(name);
  }

  function setValue(name, value) {
    GM_setValue(name, value);
  }

  function clog() {
    console.group('[YY-审核助手]')
    let args = Array.prototype.slice.call(arguments);
    args.forEach(element => {
      console.log(element);
    });
    console.groupEnd()
  }
  // 小数转化为百分数
  function toPercent(point) {
    if (point == 0) {
      return '0.00%';
    }
    var str = Number(point * 100).toFixed(2);
    str += "%";
    return str;
  }
  /**
   * canvas保存成png格式的图片
   * @param {Canvas} canvas 
   */
  function saveAsPNG(canvas) {
    return canvas.toDataURL("image/png");
  }
  /**
   * @param {String} url 下载路径
   */
  function downLoadUrl(url) {
    var dl = document.createElement("a");
    dl.download = `${batchId}-${wavId}.png`;// 设置下载的文件名，默认是'下载'
    dl.href = url;
    document.body.appendChild(dl);
    dl.click();
    dl.remove(); // 下载之后把创建的元素删除
  }

  // 二维数组转csv
  function arrayToCsv(data, args = {}) {
    let columnDelimiter = args.columnDelimiter || ',';
    let lineDelimiter = args.lineDelimiter || '\n';
    return data.reduce((csv, row) => {
      const rowContent = Array.isArray(row)
        ? row.reduce((rowTemp, col) => {
          let ret = rowTemp ? rowTemp + columnDelimiter : rowTemp;
          if (col) {
            let formatedCol = col.toString().replace(new RegExp(lineDelimiter, 'g'), ' ');
            ret += /,/.test(formatedCol) ? `"${formatedCol}"` : formatedCol;
          }
          return ret;
        }, '')
        : row;
      return (csv ? csv + lineDelimiter : '') + rowContent;
    }, '');
  }
  // 导出csv文件
  const BOM = '\uFEFF';
  function exportCsv(inputData, filename = 'export.csv') {
    const csv = arrayToCsv(inputData);
    if (navigator.msSaveOrOpenBlob) {
      let blob = new Blob([BOM + csv], { type: 'text/csv;charset=utf-8;' });
      navigator.msSaveOrOpenBlob(blob, filename);
    } else {
      let uri = encodeURI(`data:text/csv;charset=utf-8,${BOM}${csv}`);
      let downloadLink = document.createElement('a');
      downloadLink.href = uri;
      downloadLink.download = filename;
      document.body.appendChild(downloadLink);
      downloadLink.click();
      document.body.removeChild(downloadLink);
    }
  }
  //获取语音包下所有语音。
  function getLabelPackage() {
    let url = label_page_url;
    if (role_type == 'sample') {
      url = sample_page_url;
    }
    GM_xmlhttpRequest({
      method: "GET",
      url: url,
      responseType: 'json',
      onload: function (r) {
        clog(`分页数据url是：${url}`);
        $('#lh-result').children().remove();
        let res = r.response
        clog(res);
        let rd = new Array();
        res.data.list.forEach(function (ele, index) {
          let e = { 'index': index, 'wav': ele.name, 'result': RESULT_WAITING, 'reason': '', 'update_time': ele.update_time, 'time': new Date().getTime() }
          rd.push(e);
          addDivToResult(ele.name, index, RESULT_WAITING);
        });
        if (rd.length) {
          setValue(batchId, rd);
        }
      }
    });
  }
  // 将单个的结果div填充到结果区域
  function addDivToResult(wav, index, result) {
    let $ele_div = $('<div/>').addClass('lh-result').text(index + 1);
    $ele_div.attr('wav', wav);
    switch (result) {
      case RESULT_WAITING:
        $ele_div.css('background-color', 'blue')
        break;
      case RESULT_RIGHT:
        $ele_div.css('background-color', 'green')
        break;
      case RESULT_WRONG:
        $ele_div.css('background-color', 'red')
        break;
    }
    $ele_div.click(() => {
      location.href = `https://label.yy.com/${uname}/containers/${batchId}/${role_type}/${wav}`;
    });
    $('#lh-result').append($ele_div);
  }
  function AuditPlugin() {
    this.init = () => {
      this.main()
      this.addGMStyle()
    }

    this.main = () => {
      let $container = $('<div class="lh-container" id="dm-notif"></div>');
      let $div_result = $('<div id="lh-result" class="lh-result-content"></div>');
      // 标题
      $container.append(`<div style="font-weight:bold;font-size:20px;text-align:center;">${batchId}</div>`)
      // 语音列表
      $container.append($div_result);
      // 按钮
      let $btn_right = $('<button type="button" class="lh-btn">正确(Shift+R)</button>');
      let $btn_wrong = $('<button type="button" class="lh-btn">错误(Shift+W)</button>');
      let $btn_del = $('<button type="button" class="lh-btn">重新审核</button>').click(() => {
        if (confirm('是否确认重新开始审核？当前语音包中所有审核记录将会被重置！')) {
          GM_deleteValue(batchId)
          getLabelPackage();
        }
      });
      let $btn_clear = $('<button type="button" class="lh-btn">清空</button>').click(() => {
        let keys = GM_listValues();
        let str = '是否清除本机存储的';
        keys.forEach(ele => {
          str += ' ['
          str += ele;
          str += '] ';
        });
        str += '的审核记录？'
        if (confirm(str)) {
          keys.forEach(ele => {
            GM_deleteValue(ele)
          });
        }
      });
      let $btn_export = $('<button type="button" class="lh-btn">导出结果</button>').click(() => {
        this.export();
      });
      // 错误截图功能
      let $btn_capture = $('<button type="button" class="lh-btn">截图(Shift+P)</button>').click(() => {
        this.captureErrorImage();
      });
      let $test_div = $("<div/>").append($btn_export).append($btn_del).append($btn_clear).append($btn_capture);
      // 按钮外部的div
      let $btn_div = $("<div/>").append($btn_right).append($btn_wrong);
      // 添加错误原因 begin
      let $reason_div = $('<div/>').attr('id', 'lh-wrong-reason')
      $.each(WRONG_REASON, (index, item) => {
        if (index > 0) {
          let $span = $('<span/>').append('<input type="checkbox" id="rs_chk' + index + '" value="' + index + '" title="' + item + '"/>').append(index + '.' + item);
          $reason_div.append($span);
        }
      });
      // 添加错误原因 end
      $container.append($reason_div).append($btn_div).append($test_div);
      let $root = $("#root").append($container);

      // 按钮事件绑定
      $btn_right.click(() => {
        clog(`batchId=${batchId},wavId=${wavId},result=${RESULT_RIGHT}`);
        this.recordResult(wavId, RESULT_RIGHT);
      });
      $btn_wrong.click(() => {
        clog(`batchId=${batchId},wavId=${wavId},result=${RESULT_WRONG}`);
        this.recordResult(wavId, RESULT_WRONG);
      });
      // 绑定快捷键
      $(document).keypress((e) => {
        let keyCode = e.keyCode || e.which || e.charCode;
        let altKey = e.altKey;
        let ctrlKey = e.ctrlKey;
        let shiftKey = e.shiftKey;
        clog(`document keypress: keycode=${e.keyCode},alt=${altKey},ctrl=${ctrlKey},shift=${shiftKey}`);
        // 整个文档接收到了快捷键操作
        if (shiftKey && keyCode == KEY_CODE_RIGHT) {
          $btn_right.click();
        }
        if (shiftKey && keyCode == KEY_CODE_WRONG) {
          $btn_wrong.click();
        }
        if (shiftKey && keyCode == KEY_BOARD_CODE.P) {
          $btn_capture.click();
        }
        if (!shiftKey && keyCode == KEY_BOARD_CODE.MINI_PLUS) {//下一段
          $('button.MuiIconButton-root[title="Next File"]').get(0).dispatchEvent(MouseClickEvent);
        }
        if (!shiftKey && keyCode == KEY_BOARD_CODE.MINI_MINUS) {//上一段
          $('button.MuiIconButton-root[title="Previous File"]').get(0).dispatchEvent(MouseClickEvent);
        }
        // 错误原因
        $.each(WRONG_REASON, (index, item) => {
          if (index > 0) {
            if (keyCode == (KEY_BOARD_CODE.ZERO + index)) {
              $('#rs_chk' + index).prop('checked', !($('#rs_chk' + index).prop('checked')))
            }
          }
        });
      });
    };
    // 保存错误截图
    this.captureErrorImage = () => {
      // 生成图片
      let selector = 'div.editor';
      //let selector = 'table.MuiTable-root';
      new html2canvas($(selector).get(0)).then(canvas => {
        // canvas为转换后的Canvas对象
        // let oImg = new Image();
        // oImg.src = canvas.toDataURL();  // 导出图片
        // document.body.appendChild(oImg);  // 将生成的图片添加到body
        // 保存图片到本地
        downLoadUrl(saveAsPNG(canvas));
      });
    }
    // 保存单个审核结果
    this.recordResult = (wavId, result) => {
      let res = getValue(batchId);
      if (res) {//存在
        for (let i = 0; i < res.length; i++) {
          if (res[i].wav == wavId) {//标记
            // 记录错误原因开始
            res[i].reason = '';
            res[i].reason_desc = '';
            if (RESULT_WRONG == result) {
              let $chks = $('#lh-wrong-reason').find(':checkbox:checked');
              if ($chks.length) {
                $chks.each(function (index, item) {
                  if (index > 0) {
                    res[i].reason += ',';
                    res[i].reason_desc += '，'
                  }
                  res[i].reason += item.value;
                  res[i].reason_desc += item.title;
                });
                clog(`${wavId} 错误原因是：${res[i].reason},错误描述是：${res[i].reason_desc}`);
              }
            }
            // 记录错误原因结束
            res[i].result = result;
            res[i].time = new Date().getTime();
            break;
          }
        }
        setValue(batchId, res);
        // 修改div区域状态
        let $ele_div = $('#lh-result').children('div.lh-result[wav="' + wavId + '"]');
        switch (result) {
          case RESULT_RIGHT:
            $ele_div.css('background-color', 'green')
            break;
          case RESULT_WRONG:
            $ele_div.css('background-color', 'red')
            break;
        }
      } else {
        alert('请先初始化该语音包！');
      }
    }
    // 加载当前
    this.loadCurrent = (ele) => {
      // $('#dm-notif').children('div:first').text($('#dm-notif').children('div:first').text()+'-'+(ele.index+1));
      $('#dm-notif').children('div:first').text((index, text) => {
        let tag = '-';
        switch (ele.result) {
          case RESULT_WAITING:
            tag += '待';
            break;
          case RESULT_RIGHT:
            tag += '对';
            break;
          case RESULT_WRONG:
            tag += '错';
            break;
        }
        return text + '-' + (ele.index + 1) + tag;
      });
      // 勾选错误原因
      if (ele.reason) {
        let rarray = ele.reason.split(',');
        $.each(rarray, (i, item) => {
          $('#rs_chk' + item).prop('checked', 'true');
        });
      }
    }
    // 导出结果
    this.export = () => {
      let autidResult = getValue(batchId)
      //1. 转换成二维数组
      let data = [];
      data.push(['编号', '音频名称', '最后更新时间', '审核结果', '错误原因', '审核时间']);
      let right_count = 0;
      let wrong_count = 0;
      autidResult.forEach((ele) => {
        let ay = [ele.index + 1, ele.wav, ele.update_time, ele.result, ele.reason_desc, new Date(ele.time).Format('yyyy年MM月dd日hh:mm:ss.S')];
        if (ele.result == RESULT_RIGHT) {
          right_count++;
        }
        if (ele.result == RESULT_WRONG) {
          if (ele.reason.indexOf('5') == -1) {//5号错误不算
            wrong_count++;
            if (ele.reason.indexOf('1') >= 0 || ele.reason.indexOf('3') >= 0) {//1,3错误加倍
              wrong_count++;
            }
          }
        }
        data.push(ay);
      });
      let export_header = ['正确总数：', right_count, '错误总数：', wrong_count, '音频总数：', autidResult.length];
      data.splice(0, 0, export_header);
      //2. 调用导出方法
      exportCsv(data, `${batchId}审核结果${new Date().Format('yyyyMMddhhmmss')}.csv`)
    }

    this.addGMStyle = () => {
      GM_addStyle(`
      .lh-btn {color: #fff;background-color: #007bff;border-color: #007bff;padding: .375rem .75rem;border-radius: .25rem;}
      .lh-container {position: fixed;bottom: 5px;right: 5px;margin-left: 5px;z-index: 3000;width:226px;background-color:pink;overflow:hidden;}
      .lh-result-content{overflow:auto; height:250px;}
      .lh-result{float:left; width:30px; height:20px;line-height:20px;background-color:blue;text-align:center;color:white;cursor:pointer;margin:1px;}
      `)
    };
  }
  // 程序入口
  if ($("#root").length) {
    let plugin = new AuditPlugin();
    plugin.init();
    clog(`审核批次:${batchId},审核音频:${wavId}`);
    let res = getValue(batchId);
    if (!res) {
      clog('未加载过标签库，初次加载')
      getLabelPackage();//本地没有审核结果，初始化所有
    } else {
      //本地有加载过
      res.forEach(function (ele) {
        addDivToResult(ele.wav, ele.index, ele.result);
        // 直接加载审核记录
        if (wavId == ele.wav) {
          plugin.loadCurrent(ele);
        }
      });
    }
  }
  // 对Date的扩展，将 Date 转化为指定格式的String   
  // 月(M)、日(d)、小时(H)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符，   
  // 年(y)可以用 1-4 个占位符，毫秒(S)只能用 1 个占位符(是 1-3 位的数字)   
  // 例子：   
  // (new Date()).Format("yyyy-MM-dd HH:mm:ss.S") ==> 2006-07-02 08:09:04.423   
  // (new Date()).Format("yyyy-M-d H:m:s.S")      ==> 2006-7-2 8:9:4.18   
  Date.prototype.Format = function (fmt) {
    let o = {
      "M+": this.getMonth() + 1,                 //月份   
      "d+": this.getDate(),                    //日   
      "h+": this.getHours(),                   //小时   
      "m+": this.getMinutes(),                 //分   
      "s+": this.getSeconds(),                 //秒   
      "q+": Math.floor((this.getMonth() + 3) / 3), //季度   
      "S": this.getMilliseconds()             //毫秒   
    };
    if (/(y+)/.test(fmt))
      fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (let k in o)
      if (new RegExp("(" + k + ")").test(fmt))
        fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
    return fmt;
  }
})();
